#include <drivers/uart.h>
#include <kernel/initcall.h>
#include <drivers/irq.h>
#include <drivers/gpio.h>
#include <kernel/syslog.h>
#include <drivers/platform.h>
#include <kernel/fdt.h>
#include <drivers/clock.h>


struct uart_hw {
	unsigned int dr;
	unsigned int rsr;
	unsigned int unused1[4];
	unsigned int fr;
	unsigned int unuded2;
	unsigned int ilpr;
	unsigned int ibrd;
	unsigned int fbrd;
	unsigned int lcr_h;
	unsigned int cr;
	unsigned int ifls;
	unsigned int imsc;
	unsigned int ris;
	unsigned int mis;
	unsigned int icr;
};


#define FR_TXFE		(1 << 7)
#define FR_RXFF		(1 << 6)
#define FR_TXFF		(1 << 5)
#define FR_RXFE		(1 << 4)
#define FR_BUSY		(1 << 3)

#define LCR_WLEN_5	(0 << 5)
#define LCR_WLEN_6	(1 << 5)
#define LCR_WLEN_7	(2 << 5)
#define LCR_WLEN_8	(3 << 5)
#define LCR_FEN		(1 << 4)

#define CR_RXE		(1 << 9)
#define CR_TXE		(1 << 8)
#define CR_UARTEN	(1 << 0)

#define IMSC_RXIM   (1 << 4)
#define ICR_RXIC    (1 << 4)

static void rp2040_uart_init(struct uart_master *master, uint64_t baudrate)
{
	volatile struct uart_hw *uart = (volatile struct uart_hw*)master->io_base;

    uint32_t baud_rate_div = (8 * 200000000 / baudrate);
    uint32_t baud_ibrd = baud_rate_div >> 7;
    uint32_t baud_fbrd;

    if (baud_ibrd == 0) {
        baud_ibrd = 1;
        baud_fbrd = 0;
    } 
    else if (baud_ibrd >= 65535) {
        baud_ibrd = 65535;
        baud_fbrd = 0;
    } 
    else {
        baud_fbrd = ((baud_rate_div & 0x7f) + 1) / 2;
    }

	uart->cr = 0;
	uart->ibrd = baud_ibrd;
	uart->fbrd = baud_fbrd;
    // Configure FIFO watermarks
    uart->lcr_h = LCR_WLEN_8;
    uart->cr = CR_UARTEN | CR_RXE | CR_TXE;
    // Enable RX interrupt
    uart->imsc = IMSC_RXIM;
    // Clear any pending interrupts
    uart->icr = ICR_RXIC;
}


static void rp2040_uart_start(struct uart_device *dev)
{

}


static void rp2040_uart_stop(struct uart_device *dev)
{

}


static void rp2040_uart_put_char(struct uart_device *dev, char ch)
{
	volatile struct uart_hw *uart = (volatile struct uart_hw*)dev->master->io_base;
	while (uart->fr & FR_TXFF);
	uart->dr = ch;
}


static void rp2040_uart_put_string(struct uart_device *dev, char *str)
{
	volatile struct uart_hw *uart = (volatile struct uart_hw*)dev->master->io_base;

	while(*str) {
		while (uart->fr & FR_TXFF);
		uart->dr = *str;
        str++;
	}
}


static char rp2040_uart_get_char(struct uart_device *dev)
{
	volatile struct uart_hw *uart = (volatile struct uart_hw*)dev->master->io_base;
    uart->icr = ICR_RXIC;
    while (uart->fr & FR_RXFE);
    return (uart->dr & 0xFF);
}


static void rp2040_uart_set_baudrate(struct uart_device *dev, uint32_t baudrate)
{

}


static struct uart_ops rp2040_uart_ops = {
    .start = rp2040_uart_start,
    .stop = rp2040_uart_stop,
    .put_char = rp2040_uart_put_char,
    .put_string = rp2040_uart_put_string,
    .get_char = rp2040_uart_get_char,
    .set_baudrate = rp2040_uart_set_baudrate,
};


static int rp2040_uart_probe(struct platform_device *dev)
{
    fdt_node_t *uart_node = dev->device.dt_node;
    const char *uart_clock = fdt_read_prop_string(uart_node, "clock-bus");
    if(uart_clock == NULL) {
        return -1;
    }

    size_t clock_bit = -1;
    if(fdt_read_prop_int(uart_node, "clock-bit", &clock_bit)) {
        PLATFORM_SYSLOG_ERROR("uart: no clock-bit property");
        return -1;
    }

    clock_enable_by_name(uart_clock, clock_bit);

    size_t irq_num = 0;
    if(fdt_read_prop_int(uart_node, "interrupts", &irq_num)) {
        PLATFORM_SYSLOG_ERROR("uart: no interrupts property");
    }

    size_t baudrate = 0;
    if(fdt_read_prop_int(uart_node, "baudrate", &baudrate)) {
        PLATFORM_SYSLOG_ERROR("uart: no baudrate property");
        return -1;
    }

    struct uart_gpio pins = {-1, -1};
    if(uart_master_get_gpio_from_fdt(uart_node, &pins)) {
        return -1;
    }

    struct uart_master *master = kmalloc(sizeof(struct uart_master));
    if(master == NULL) {
        PLATFORM_SYSLOG_ERROR("uart: kmalloc failed");
        return -1;
    }

    master->ops = &rp2040_uart_ops;
    master->pins = pins;
    master->io_base = dev->io_base;
    master->clock_freq_hz = clock_get_freq_by_name(uart_clock);

    platform_set_drvdata(dev, master);

    rp2040_uart_init(master, baudrate);

    fdt_node_t *child =  NULL;
    fdt_for_each_node_child(uart_node, child) {
        const char *comp = fdt_read_prop_string(child, "compatible");
        if(comp == NULL) {
            PLATFORM_SYSLOG_ERROR("uart: no compatible property");
            continue;
        }

        const char *status = fdt_read_prop_string(child, "status");
        if(strcmp(status, "disabled") == 0) {
            PLATFORM_SYSLOG_INFO("uart: status is disabled");
            continue;
        }

        struct uart_device *udev = kmalloc(sizeof(struct uart_device));
        if(udev == NULL) {
            PLATFORM_SYSLOG_ERROR("uart: kmalloc failed");
            return -1;
        }

        udev->device.name = uart_node->name;
        udev->device.compatible = comp;
        udev->device.driver = NULL;
        udev->master = master;

        if(uart_device_register(udev) < 0) {
            PLATFORM_SYSLOG_ERROR("uart: uart_device_register failed");
            kfree(udev);
            return -1;
        }

        if(irq_num) {
            udev->irq_num = irq_num;
        }
    }

    return 0;
}


static void rp2040_uart_remove(struct platform_device *dev)
{

}


static struct of_device_id rp2040_uart_match[] = {
    {.compatible = "rp2040-uart", },
    { }
};


static struct platform_driver rp2040_uart_driver = {
    .driver = {
        .name = "rp2040-uart",
        .of_match_table = rp2040_uart_match,
    },
    .probe = rp2040_uart_probe,
    .remove = rp2040_uart_remove,
};


static int rp2040_uart_driver_register(void)
{
    return platform_driver_register(&rp2040_uart_driver);
}


core_initcall(rp2040_uart_driver_register);
